
package com.chongchong.multifile.service.impl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chongchong.common.cache.CacheNames;
import com.chongchong.common.enums.NoTypeEnum;
import com.chongchong.common.tool.CommonUtil;
import com.chongchong.multifile.entity.Chunk;
import com.chongchong.multifile.entity.FileList;
import com.chongchong.multifile.mapper.ChunkMapper;
import com.chongchong.multifile.mapper.FileListMapper;
import com.chongchong.multifile.service.IChunkService;
import com.chongchong.core.tool.utils.DateUtil;
import com.chongchong.core.tool.utils.RedisUtil;
import com.chongchong.multifile.utils.CommonConstant;
import com.chongchong.multifile.vo.CheckChunkVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 文件分块表 服务实现类
 *
 * @author en
 * @since 2022-07-18
 */
@Service
@Slf4j
public class ChunkServiceImpl extends ServiceImpl<ChunkMapper, Chunk> implements IChunkService {
	@Value("${csixframework.profile}")
	private String filePath;
	@Autowired
	private RedisUtil redisUtil;

	private final static String folderPath = "/file";

	@Autowired(required = false)
	private FileListMapper filelistMapper;

	/**
	 * 查询文件分片管理
	 *
	 * @param id 文件分片管理ID
	 * @return 文件分片管理
	 */
	@Override
	public Chunk selectChunkById(Long id) {
		return baseMapper.selectChunkById(id);
	}

	/**
	 * 查询文件分片管理列表
	 *
	 * @param Chunk 文件分片管理
	 * @return 文件分片管理
	 */
	@Override
	public List<Chunk> selectChunkList(Chunk Chunk) {
		return baseMapper.selectChunkList(Chunk);
	}

	/**
	 * 新增文件分片管理
	 *
	 * @param Chunk 文件分片管理
	 * @return 结果
	 */
	@Override
	public int insertChunk(Chunk Chunk) {
		return baseMapper.insertChunk(Chunk);
	}

	/**
	 * 修改文件分片管理
	 *
	 * @param Chunk 文件分片管理
	 * @return 结果
	 */
	@Override
	public int updateChunk(Chunk Chunk) {
		return baseMapper.updateChunk(Chunk);
	}

	/**
	 * 批量删除文件分片管理
	 *
	 * @param ids 需要删除的文件分片管理ID
	 * @return 结果
	 */
	@Override
	public int deleteChunkByIds(Long[] ids) {
		return baseMapper.deleteChunkByIds(ids);
	}

	/**
	 * 删除文件分片管理信息
	 *
	 * @param id 文件分片管理ID
	 * @return 结果
	 */
	@Override
	public int deleteChunkById(Long id) {
		return baseMapper.deleteChunkById(id);
	}

	/**
	 * 每一个上传块都会包含如下分块信息：
	 * chunkNumber: 当前块的次序，第一个块是 1，注意不是从 0 开始的。
	 * totalChunks: 文件被分成块的总数。
	 * chunkSize: 分块大小，根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
	 * currentChunkSize: 当前块的大小，实际大小。
	 * totalSize: 文件总大小。
	 * identifier: 这个就是每个文件的唯一标示,md5码
	 * filename: 文件名。
	 * relativePath: 文件夹上传的时候文件的相对路径属性。
	 * 一个分块可以被上传多次，当然这肯定不是标准行为，但是在实际上传过程中是可能发生这种事情的，这种重传也是本库的特性之一。
	 * <p>
	 * 根据响应码认为成功或失败的：
	 * 200 文件上传完成
	 * 201 文加快上传成功
	 * 500 第一块上传失败，取消整个文件上传
	 * 507 服务器出错自动重试该文件块上传
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public int postFileUpload(Chunk chunk, HttpServletResponse response) {
		int result = CommonConstant.UPDATE_FAIL;
		MultipartFile file = chunk.getFile();
		log.debug("file originName: {}, chunkNumber: {}", file.getOriginalFilename(), chunk.getChunkNumber());
		Path path = Paths.get(generatePath(filePath + folderPath, chunk));
		try {
			Files.write(path, chunk.getFile().getBytes());
			log.debug("文件 {} 写入成功, md5:{}", chunk.getFilename(), chunk.getIdentifier());
			chunk.setIdentifier(chunk.getFilename());
			result = baseMapper.insertChunk(chunk);
			//写入数据库
		} catch (IOException e) {
			e.printStackTrace();
			response.setStatus(507);
			return CommonConstant.UPDATE_FAIL;
		}
		return result;
	}

	@Override
	public CheckChunkVO getFileUpload(Chunk chunk, HttpServletResponse response) {
		CheckChunkVO vo = new CheckChunkVO();
	/*	//检查该文件是否存在于filelist中,如果存在,直接返回skipUpload为true,执行闪传
		FileList fileList = new FileList();
		fileList.setIdentifier(chunk.getIdentifier());
		List<FileList> backFilelists = filelistMapper.selectFileListList(fileList);
		if (backFilelists != null && !backFilelists.isEmpty()) {
			response.setStatus(HttpServletResponse.SC_CREATED);
			vo.setSkipUpload(true);
			return vo;
		}
*/
		Chunk resultChunk = new Chunk();
		resultChunk.setIdentifier(chunk.getIdentifier());
		List<Chunk> Chunks = baseMapper.selectChunkList(resultChunk);
		//将已存在的块的chunkNumber列表返回给前端,前端会规避掉这些块
		if (Chunks != null && !Chunks.isEmpty()) {
			List<Integer> collect = Chunks.stream().map(Chunk::getChunkNumber).collect(Collectors.toList());
			vo.setUploaded(collect);
		}
		return vo;
	}

	@Override
	public int deleteBackFileByIds(Long id) {
		return 0;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public int mergeFile(FileList fileInfo) {
		String filename = fileInfo.getFilename();
        String no = getFileNo();
		String file = filePath + folderPath + "/" + DateUtil.format(new Date(), "yyyy-MM-dd") + "/" +no+"-"+ filename ;
		String folder = filePath + folderPath + "/" + DateUtil.format(new Date(), "yyyy-MM-dd")+"/"+fileInfo.getIdentifier();
		String url = folderPath + "/" + DateUtil.format(new Date(), "yyyy-MM-dd") + "/" +no+"-"+ filename;
		merge(file, folder, filename);
		/*//当前文件已存在数据库中时,返回已存在标识
		if (filelistMapper.selectSingleFileList(fileInfo) > 0) {
			return CommonConstant.UPDATE_EXISTS;
		}*/
		fileInfo.setLocation(file);
		fileInfo.setUrl(url);
		fileInfo.setCreateTime(new Date());
		fileInfo.setUpdateTime(new Date());
		int i = filelistMapper.insert(fileInfo);
		if (i > 0) {
			//插入文件记录成功后,删除chunk表中的对应记录,释放空间
			Chunk Chunk = new Chunk();
			Chunk.setIdentifier(fileInfo.getIdentifier());
			Chunk.setFilename(fileInfo.getFilename() );
			baseMapper.deleteChunkByIdentifier(Chunk);
		}
		return i;
	}

	public String getFileNo() {
		NoTypeEnum en = NoTypeEnum.FILE;
		String redisKey = CacheNames.NO_KEY + ":" + NoTypeEnum.getEnum(en.getNoType()).getNoPrefix() + "_" + DateUtil.format(new Date(), "yyyyMMdd");
		Long seq = redisUtil.incr(redisKey, 1);
		String no = CommonUtil.createNo(en.getNoType(), String.format("%05d", seq));
		return no;
	}

	/**
	 * 功能描述:生成块文件所在地址
	 *
	 * @param:
	 * @return:
	 * @author: xjd
	 * @date: 2020/7/28 16:10
	 */
	private String generatePath(String uploadFolder, Chunk chunk) {
		StringBuilder sb = new StringBuilder();
		//文件夹地址/md5
		sb.append(uploadFolder).append("/").append(DateUtil.format(new Date(), "yyyy-MM-dd")).append("/").append(chunk.getIdentifier());
		//判断uploadFolder/identifier 路径是否存在，不存在则创建
		if (!Files.isWritable(Paths.get(sb.toString()))) {
			log.info("path not exist,create path: {}", sb.toString());
			try {
				Files.createDirectories(Paths.get(sb.toString()));
			} catch (IOException e) {
				log.error(e.getMessage(), e);
			}
		}
		//文件夹地址/md5/文件名-1
		return sb.append("/")
			.append(chunk.getFilename())
			.append("-")
			.append(chunk.getChunkNumber()).toString();
	}

	/**
	 * 文件合并
	 *
	 * @param targetFile 要形成的文件名
	 * @param folder     要形成的文件夹地址
	 * @param filename   文件的名称
	 */
	public static void merge(String targetFile, String folder, String filename) {
		try {
			Files.createFile(Paths.get(targetFile));
			Files.list(Paths.get(folder))
				.filter(path -> !path.getFileName().toString().equals(filename))
				.sorted((o1, o2) -> {
					String p1 = o1.getFileName().toString();
					String p2 = o2.getFileName().toString();
					int i1 = p1.lastIndexOf("-");
					int i2 = p2.lastIndexOf("-");
					return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
				})
				.forEach(path -> {
					try {
						//以追加的形式写入文件
						Files.write(Paths.get(targetFile), Files.readAllBytes(path), StandardOpenOption.APPEND);
						//合并后删除该块
						Files.delete(path);
					} catch (IOException e) {
						log.error(e.getMessage(), e);
					}
				});
			Files.delete(Paths.get(folder));
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
	}


}
